﻿using System;
using System.IO;
using MLSharp.Classification;
using MLSharp.Utilities;

namespace MLSharp.Cpn
{
	/// <summary>
	/// A counter-propagation network.
	/// </summary>
	public class CounterPropagationNetwork : INeuralNetClassifier
	{
		#region Private Static Fields

		/// <summary>
		/// The logger for the type.
		/// </summary>
		private static readonly log4net.ILog mLogger =
			log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		#endregion

		#region Private Fields

		/// <summary>
		/// The working directory to use for temp files.
		/// </summary>
		private readonly string mWorkingDirectory;

		/// <summary>
		/// The path to cpn.exe.
		/// </summary>
		private readonly string mCpnPath;

		#endregion

		#region Public Properties

		/// <summary>
		/// The command to create the network structure.
		/// </summary>
		public string CreateNetworkCommand { get; private set; }

		/// <summary>
		/// The command to initialize the weights.
		/// </summary>
		public string Weights { get; set; }

		#endregion

		#region Public Constructors

		/// <summary>
		/// Creates a counter-propagation network.
		/// </summary>
		/// <param name="createNetworkCommand"></param>
		/// <param name="weights"></param>
		/// <param name="cpnPath"></param>
		/// <param name="workingDirectory"></param>
		public CounterPropagationNetwork(string createNetworkCommand, string weights, string cpnPath, string workingDirectory)
		{
			CreateNetworkCommand = createNetworkCommand;
			Weights = weights;
			mCpnPath = cpnPath;
			mWorkingDirectory = workingDirectory;
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Classifies all the instances in the specified data set.
		/// </summary>
		/// <param name="dataSet">The data set to classify.</param>
		/// <returns>The results of classifying each instance.</returns>
		public ClassificationResult[] Classify(IDataSet dataSet)
		{
			//This is the actual CPN script that will be generated.
			string cpnScriptName = Path.Combine(mWorkingDirectory, NameGenerator.GetRandomName(10) + ".cpn");
			//And these are the testing vectors
			string cpnInputTableName = Path.Combine(mWorkingDirectory, NameGenerator.GetRandomName(10) + ".testing");
			//And this is where the weights will be written.
			string weightsFile = Path.Combine(mWorkingDirectory, NameGenerator.GetRandomName(10) + ".weights");

			//Write the training data.
			CpnDataWriter.WriteTrainingData(dataSet, cpnInputTableName);

			//Write the weights to the file
			File.WriteAllText(weightsFile, Weights);

			using (StreamWriter writer = File.CreateText(cpnScriptName))
			{
				//Turn off paging
				writer.WriteLine("f P 0");

				//Initialize the network
				writer.WriteLine(CreateNetworkCommand);

				//Read the weights
				writer.WriteLine("rw {0}", weightsFile);

				//Read the training data
				writer.WriteLine("tf {0}", cpnInputTableName);

				//Run the training data
				writer.WriteLine("t");

				//Exit
				writer.WriteLine("q");
			}

			//Run CPN
			string[] output =
				ProcessWrapper.Run(mCpnPath, mWorkingDirectory, "\"" + cpnScriptName + "\"").StandardOutput.Split(new[] {'\n', '\r'},
				                                                                                   StringSplitOptions.
				                                                                                   	RemoveEmptyEntries);

			//Verify that the correct number of results were written
			if ((output.Length-2) != dataSet.Instances.Count)
			{
				string message = string.Format("Expected {0} results, found {1}.", dataSet.Instances.Count, output.Length-2);

				if (mLogger.IsErrorEnabled)
				{
					mLogger.Error("Bad results found, dumping output: " + string.Join("\r\n", output));
				}

				throw new InvalidOperationException(message);
			}

			ClassificationResult[] results = new ClassificationResult[dataSet.Instances.Count];

			//Loop through and process the results.
			for (int i=0; i < output.Length - 2; i++)
			{
				//Grab an ID for this instance, if any.
				string id = dataSet.Instances[i].Label;

				//We use i+1 here because we want to skip the header line.
				double value = double.Parse(output[i+1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]);

				//Find the best class for this instance.
				string classification = dataSet.Attributes[dataSet.TargetAttributeIndex].GetClosestValue(value);

				double confidence = dataSet.Attributes[dataSet.TargetAttributeIndex].GetConfidenceForClosestValue(value);

				results[i] = new ClassificationResult(classification, dataSet.Instances[i].ClassValue)
				{
					Confidence = confidence,
					ID = id
				};
			}

			return results;
		}

		#endregion
	}
}
